{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "12000\n"
     ]
    }
   ],
   "source": [
    "money = 100000\n",
    "price = 8.30\n",
    "vol = money / price\n",
    "result = round(vol / 100)*100  # 四舍五入到最接近的百位数\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "移动平均线和止损策略的backtrader策略示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据已写入 output.csv\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# 定义包含列名的列表\n",
    "symbol_selected = ['1', '2', '3']\n",
    "\n",
    "symbol_selected=pd.DataFrame(symbol_selected)\n",
    "# 定义 CSV 文件的路径\n",
    "csv_file_path = 'output.csv'\n",
    "\n",
    "# 将 DataFrame 写入 CSV 文件\n",
    "symbol_selected.to_csv(csv_file_path, index=False)\n",
    "\n",
    "print(f\"数据已写入 {csv_file_path}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd  \n",
    "datdata_rows = []  a_strings = [ \"日期: 2018-05-29 , 执行: 执行买入,股票代码: 000001, 成交量: 500.0,成交价: 1778.03, 当前资产: 981971.97 \",\n",
    "                 \"日期: 2018-06-29 , 执行: 执行买入,股票代码: 000001, 成交量: 500.0,成交价: 1778.03, 当前资产: 981971.97 \",\n",
    "                 \"日期: 2018-05-29 , 执行: 执行买入,股票代码: 000001, 成交量: 500.0,成交价: 1778.03, 当前资产: 981971.97 \",\n",
    "                ]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [],
   "source": [
    "for data_str in data_strings:  \n",
    "    #使用 split 方法将字符串 data_str 根据逗号 , 分割成一个列表 parts。\n",
    "    parts = data_str.split(',')  # 按逗号和空格分割字符串  \n",
    "    # 创建一个空的字典（dictionary）并将它赋值给变量 row_data。\n",
    "    row_data = {} \n",
    "    for part in parts:  \n",
    "        key_value = part.split(':')  # 尝试按冒号和空格分割键值对  \n",
    "        if len(key_value) == 2:  # 确保分割后有两个元素  \n",
    "            key, value = key_value  \n",
    "            row_data[key] = value.strip()  # 去除值两侧的空白字符  \n",
    "        else:  \n",
    "            # 处理分割后元素数量不是2的情况，例如打印警告或跳过该键值对  \n",
    "            print(f\"Warning: Invalid key-value pair: {part}\")  \n",
    "              \n",
    "    # 将处理后的字典添加到列表中  \n",
    "    data_rows.append(row_data)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(data_rows) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>日期</th>\n",
       "      <th>执行</th>\n",
       "      <th>股票代码</th>\n",
       "      <th>成交量</th>\n",
       "      <th>成交价</th>\n",
       "      <th>当前资产</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2018-05-29</td>\n",
       "      <td>执行买入</td>\n",
       "      <td>000001</td>\n",
       "      <td>500.0</td>\n",
       "      <td>1778.03</td>\n",
       "      <td>981971.97</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2018-06-29</td>\n",
       "      <td>执行买入</td>\n",
       "      <td>000001</td>\n",
       "      <td>500.0</td>\n",
       "      <td>1778.03</td>\n",
       "      <td>981971.97</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2018-05-29</td>\n",
       "      <td>执行买入</td>\n",
       "      <td>000001</td>\n",
       "      <td>500.0</td>\n",
       "      <td>1778.03</td>\n",
       "      <td>981971.97</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           日期    执行    股票代码    成交量      成交价       当前资产\n",
       "0  2018-05-29  执行买入  000001  500.0  1778.03  981971.97\n",
       "1  2018-06-29  执行买入  000001  500.0  1778.03  981971.97\n",
       "2  2018-05-29  执行买入  000001  500.0  1778.03  981971.97"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd  \n",
    "data_strings = [ \"日期: 2018-05-29 , 执行: 执行买入,股票代码: 000001, 成交量: 500.0,成交价: 1778.03, 当前资产: 981971.97\"]\n",
    "data_rows = []  \n",
    "  \n",
    "for data_str in data_strings:  \n",
    "    parts = data_str.split(',')  # 按逗号和空格分割字符串  \n",
    "    # 创建一个空的字典（dictionary）并将它赋值给变量 row_data。\n",
    "    row_data = {}  \n",
    "    for part in parts:  \n",
    "        key_value = part.split(':')  # 尝试按冒号和空格分割键值对  \n",
    "        if len(key_value) == 2:  # 确保分割后有两个元素  \n",
    "            key, value = key_value  \n",
    "            row_data[key] = value.strip()  # 去除值两侧的空白字符  \n",
    "        else:  \n",
    "            # 处理分割后元素数量不是2的情况，例如打印警告或跳过该键值对  \n",
    "            print(f\"Warning: Invalid key-value pair: {part}\")  \n",
    "              \n",
    "    # 将处理后的字典添加到列表中  \n",
    "    data_rows.append(row_data)  \n",
    "  \n",
    "# 创建一个DataFrame  \n",
    "df = pd.DataFrame(data_rows)  \n",
    "\n",
    "# 打印DataFrame查看结果  \n",
    "print(df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "stock_hfq_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 时间索引\n",
    "df.index = pd.to_datetime(df['date'])\n",
    "print(df.tail(1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 转换akshare数据为backtrader DataFeed  \n",
    "class AkshareDataFeed(bt.feeds.PandasData):  \n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期时间列在DataFrame中的位置  \n",
    "        ('open', 1),      # 开盘价列在DataFrame中的位置  \n",
    "        ('high', 2),      # 最高价列在DataFrame中的位置  \n",
    "        ('low', 3),       # 最低价列在DataFrame中的位置  \n",
    "        ('close', 4),     # 收盘价列在DataFrame中的位置  \n",
    "        ('volume', 5),    # 交易量列在DataFrame中的位置  \n",
    "        #('openinterest', -1),  # 持仓量列在DataFrame中的位置（如果不存在，则设置为-1）  \n",
    "    )  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 自定义指标，计算收盘价的n日差值  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 自定义指标，计算收盘价的n日差值  \n",
    "class DiffClose(bt.Indicator):  \n",
    "    lines = ('diff',)  \n",
    "    params = (('period', 5),)  \n",
    "  \n",
    "    def next(self):  \n",
    "        if self.getdatasize() > self.p.period:  \n",
    "            self.lines.diff[0] = self.data.close[0] - self.data.close[-self.p.period]  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建Cerebro引擎  \n",
    "cerebro = bt.Cerebro()  \n",
    "  \n",
    "# 添加DataFeed到引擎  \n",
    "data = AkshareDataFeed(dataname=df)  \n",
    "cerebro.adddata(data) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 添加自定义指标  \n",
    "cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')  \n",
    "cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 添加策略（这里仅作为示例，没有实际交易逻辑）  \n",
    "class TestStrategy(bt.Strategy):  \n",
    "    def __init__(self):  \n",
    "        self.diff = DiffClose(self.data)  \n",
    "  \n",
    "    def next(self):  \n",
    "        # 这里可以添加基于diff的交易逻辑  \n",
    "        pass  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cerebro.addstrategy(TestStrategy)  \n",
    "  \n",
    "# 运行回测  \n",
    "results = cerebro.run()  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import (absolute_import, division, print_function,\n",
    "                        unicode_literals)\n",
    "\n",
    "import backtrader as bt\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    cerebro = bt.Cerebro()\n",
    "\n",
    "    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())\n",
    "\n",
    "    cerebro.run()\n",
    "\n",
    "    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "\n",
    "class MovingAverageStopLossStrategy(bt.Strategy):\n",
    "    params = (\n",
    "        ('period', 10),  # 移动平均线的周期\n",
    "        ('stop_loss', 0.03),  # 止损比例，比如3%\n",
    "    )\n",
    "    \n",
    "    def __init__(self):\n",
    "        # 添加简单移动平均线指标\n",
    "        # self.sma = bt.indicators.SimpleMovingAverage(self.data.close, period=self.params.period)\n",
    "        \n",
    "        self.sma = bt.indicators.SimpleMovingAverage(\n",
    "            self.datas[0], period=self.params.period\n",
    "        )\n",
    "        # 记录买入价格\n",
    "        self.buy_price = None\n",
    "\n",
    "    def next(self):\n",
    "        # 计算止损价格\n",
    "        stop_price = self.buy_price * (1 - self.params.stop_loss) if self.buy_price else None\n",
    "\n",
    "        \n",
    "        # 检查当前是否有持仓\n",
    "        if self.position:\n",
    "            # 如果当前价格低于止损价格，则卖出\n",
    "            if self.data.close[0] < stop_price:\n",
    "                self.log('止损卖出')\n",
    "                print(f'{ self.data.datetime.date(0)}, {stop_price},{self.data0.close[0]}')\n",
    "                \n",
    "                self.close()\n",
    "            elif self.data.close[0] < self.sma[0]:\n",
    "                self.log('卖出')\n",
    "                print(f'{ self.data.datetime.date(0)}, {stop_price},{self.data0.close[0]}')\n",
    "                self.close()  \n",
    "                  \n",
    "            return\n",
    "        \n",
    "            \n",
    "           \n",
    "\n",
    "        # 检查是否应该买入\n",
    "        if not self.position:\n",
    "            # 当收盘价从下方穿越移动平均线时买入\n",
    "            if self.data.close[0] > self.sma[0]:\n",
    "                # 执行买入操作\n",
    "                self.buy()\n",
    "                self.log('买入')\n",
    "                print(f'{ self.data.datetime.date(0)}, {stop_price},{self.data0.close[0]}')\n",
    "                # 更新买入价格\n",
    "                self.buy_price = self.data.close[0]\n",
    "\n",
    "    def log(self, txt, dt=None):\n",
    "        dt = dt or self.data.datetime.date(0)\n",
    "        print(f'{dt.isoformat()}, {txt}')\n",
    "\n",
    "# 创建cerebro，即backtrader的回测引擎\n",
    "cerebro = bt.Cerebro()\n",
    "start_date = datetime(2023, 4, 3)\n",
    "end_date = datetime(2024, 5, 22)\n",
    "  \n",
    "# 添加数据源  \n",
    "# data0 = MyStockData(dataname=data_stock, fromdate=start_date, todate=end_date) \n",
    "data = bt.feeds.PandasData(dataname=df, fromdate=start_date, todate=end_date)\n",
    "\n",
    "\n",
    "cerebro.adddata(data)\n",
    "\n",
    "# 添加策略\n",
    "cerebro.addstrategy(MovingAverageStopLossStrategy)\n",
    "\n",
    "# 设置初始资金和手续费\n",
    "start_cash = 1000000\n",
    "cerebro.broker.setcash(start_cash)\n",
    "#cerebro.broker.setcommission(commission=0.002)\n",
    "\n",
    "# 添加策略分析指标\n",
    "cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='tradeanalyzer')  \n",
    "cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annualReturn')  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='annualizedReturns', tann=252)  # 使用 'annualizedReturns' 代替 '_Returns'  \n",
    "cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')  \n",
    "cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpeRatio')  # 使用 'sharpeRatio' 代替 'sharpe'  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='totalReturns')  # 使用 'totalReturns' 代替重复的 'returns'  \n",
    "# 假设 'TimeReturn' 是有效的分析器  \n",
    "cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='timeReturn')  # 假设这是有效的\n",
    "\n",
    "# 运行回测\n",
    "results=cerebro.run()\n",
    "\n",
    "# 绘制结果\n",
    "cerebro.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 输出结果  \n",
    "#获取回测结果并打印\n",
    "port_value = cerebro.broker.getvalue()\n",
    "pnl = port_value - start_cash\n",
    "print(f\"初始资金: {start_cash}\\n回测期间：{start_date.strftime('%Y%m%d')}:{end_date.strftime('%Y%m%d')}\")\n",
    "print(f\"总资金: {round(port_value, 2)}\")\n",
    "print(f\"净收益: {round(pnl, 2)}\")#计算胜率\n",
    "total_trades = results[0].analyzers.tradeanalyzer.get_analysis()['total']['total']\n",
    "won_trades = results[0].analyzers.tradeanalyzer.get_analysis()['won']['total']\n",
    "win_rate = (won_trades / total_trades) * 100 if total_trades > 0 else 0\n",
    "print('总交易次数:', total_trades)\n",
    "print('盈利次数:', won_trades)\n",
    "print('胜率%:', win_rate)\n",
    "# 打印分析器输出结果\n",
    "# print(f\"初始资金: {start_cash}\\n回测期间:{start_date.strftime('%Y-%m-%d')} : {end_date.strftime('%Y-%m-%d')}\")\n",
    "print('年度汇报:', results[0].analyzers.annualReturn.get_analysis())\n",
    "print('年化收益%:', results[0].analyzers.annualizedReturns.get_analysis()['rnorm100'])\n",
    "print('最大回撤比例%:', results[0].analyzers.drawdown.get_analysis().max.drawdown)\n",
    "print('夏普比率:', results[0].analyzers.sharpeRatio.get_analysis()['sharperatio'])\n",
    "print('累计收益：', results[0].analyzers.totalReturns.get_analysis()['rtot'])\n",
    "\n",
    "print(f'最后投资金额：{round(cerebro.broker.getvalue(), 2)}')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from backtrader_plotting import Bokeh\n",
    "from backtrader_plotting.schemes import Tradimo\n",
    "\n",
    "plotconfig = {\n",
    "    'id:ind#0': dict(\n",
    "        subplot=True,\n",
    "    ),\n",
    "}\n",
    "b = Bokeh(style='line', scheme=Tradimo(),plotconfig=plotconfig)\n",
    "cerebro.plot(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 获取除了'symbol'列之外的所有列名  \n",
    "original_cols = [col for col in df.columns if col != 'symbol']  \n",
    "  \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "original_cols"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 重新排序列的顺序，将'symbol'列放在'date'列之后  \n",
    "new_cols = ['date', 'symbol'] + [col for col in original_cols if col != 'date']  \n",
    "  \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_cols# 重新排序列的顺序，将'symbol'列放在'date'列之后  \n",
    "new_cols = ['date', 'symbol'] + [col for col in original_cols if col != 'date']  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用新的列顺序重新排列DataFrame的列  \n",
    "df = df[new_cols]  \n",
    "  \n",
    "# 打印DataFrame以查看结果  \n",
    "print(df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd  \n",
    "from typing import List, Any  \n",
    "from enum import Enum  \n",
    "  \n",
    "# 假设的BarData类（与之前相同）  \n",
    "class BarData:  \n",
    "    def __init__(self, date: List[Any], open_price: List[Any], high_price: List[Any],  \n",
    "                 low_price: List[Any], close_price: List[Any], volume: List[Any] = None,  \n",
    "                 vwap: List[Any] = None, **kwargs):  \n",
    "        self.date = date  \n",
    "        self.open_price = open_price  \n",
    "        self.high_price = high_price  \n",
    "        self.low_price = low_price  \n",
    "        self.close_price = close_price  \n",
    "        self.volume = volume  \n",
    "        self.vwap = vwap  \n",
    "        self.__dict__.update(kwargs)  \n",
    "  \n",
    "# 假设的DataCol枚举（与之前相同）  \n",
    "class DataCol(Enum):  \n",
    "    DATE = 'date'  \n",
    "    OPEN = 'open'  \n",
    "    HIGH = 'high'  \n",
    "    LOW = 'low'  \n",
    "    CLOSE = 'close'  \n",
    "    VOLUME = 'volume'  \n",
    "    VWAP = 'vwap'  \n",
    "  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd  \n",
    "from typing import List, Any  \n",
    "from enum import Enum  \n",
    "  \n",
    "# 假设的BarData类（与之前相同）  \n",
    "class BarData:  \n",
    "    def __init__(self, date: List[Any], open_price: List[Any], high_price: List[Any],  \n",
    "                 low_price: List[Any], close_price: List[Any], volume: List[Any] = None,  \n",
    "                 vwap: List[Any] = None, **kwargs):  \n",
    "        self.date = date  \n",
    "        self.open_price = open_price  \n",
    "        self.high_price = high_price  \n",
    "        self.low_price = low_price  \n",
    "        self.close_price = close_price  \n",
    "        self.volume = volume  \n",
    "        self.vwap = vwap  \n",
    "        self.__dict__.update(kwargs)  \n",
    "  \n",
    "# 假设的DataCol枚举（与之前相同）  \n",
    "class DataCol(Enum):  \n",
    "    DATE = 'date'  \n",
    "    OPEN = 'open'  \n",
    "    HIGH = 'high'  \n",
    "    LOW = 'low'  \n",
    "    CLOSE = 'close'  \n",
    "    VOLUME = 'volume'  \n",
    "    VWAP = 'vwap'  \n",
    "  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 示例DataFrame  \n",
    "data = {  \n",
    "    'date': ['2023-01-01', '2023-01-02', '2023-01-03'],  \n",
    "    'open': [100, 101, 102],  \n",
    "    'high': [105, 106, 107],  \n",
    "    'low': [98, 99, 100],  \n",
    "    'close': [104, 105, 106],  \n",
    "    'volume': [1000, 1200, 1100],  \n",
    "    'vwap': [102.5, 103.5, 104.5]  \n",
    "}  \n",
    "df = pd.DataFrame(data)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 实现_to_bar_data函数  \n",
    "def _to_bar_data(df: pd.DataFrame) -> BarData:  \n",
    "    date = df[DataCol.DATE.value].tolist()  \n",
    "    open_price = df[DataCol.OPEN.value].tolist()  \n",
    "    high_price = df[DataCol.HIGH.value].tolist()  \n",
    "    low_price = df[DataCol.LOW.value].tolist()  \n",
    "    close_price = df[DataCol.CLOSE.value].tolist()  \n",
    "    volume = df.get(DataCol.VOLUME.value, None)  \n",
    "    if volume is not None:  \n",
    "        volume = volume.tolist()  \n",
    "    vwap = df.get(DataCol.VWAP.value, None)  \n",
    "    if vwap is not None:  \n",
    "        vwap = vwap.tolist()  \n",
    "      \n",
    "    # 提取自定义列（可选）  \n",
    "    custom_cols = [col for col in df.columns if col not in DataCol.__members__.values()]  \n",
    "    custom_data = {col: df[col].tolist() for col in custom_cols}  \n",
    "      \n",
    "    return BarData(date, open_price, high_price, low_price, close_price, volume, vwap, **custom_data)  \n",
    "  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用_to_bar_data函数  \n",
    "bar_data = _to_bar_data(df)  \n",
    "  \n",
    "# 验证BarData对象  \n",
    "print(bar_data.date)  \n",
    "print(bar_data.open_price)  \n",
    "print(bar_data.custom_data)  # 注意：这里使用custom_data来访问自定义列数据"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# tttttttttttttttt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import backtrader as bt  \n",
    "import pandas as pd\n",
    "from datetime import datetime\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from backtrader_plotting import Bokeh\n",
    "from backtrader_plotting.schemes import Tradimo\n",
    "\n",
    "# 读取csv\n",
    "data_stock = pd.read_csv('法宝回测/data/sh.csv') # 股票日数据\n",
    "data_signals = pd.read_csv('法宝回测/data/bwwmacd.csv') # 股票指标数据(大智慧导出)\n",
    "\n",
    "# 提取需要列\n",
    "selected_columns = ['日期', '开盘', '最高', '最低', '收盘', '成交量', 'MA5', 'MA10', 'MA30', 'MA60']\n",
    "data_stock = data_stock[selected_columns]\n",
    "\n",
    "# 改变列名称\n",
    "data_stock.columns = ['date', 'open', 'high', 'low', 'close', 'volume','MA5','MA10','MA30',\t'MA60']\n",
    "data_signals.columns = ['date', 'M5', 'M10', 'M20']\n",
    "\n",
    "# 日期列转换时间类型\n",
    "data_stock['date'] = pd.to_datetime(data_stock['date'])\n",
    "data_signals['date'] = pd.to_datetime(data_signals['date'])\n",
    "\n",
    "# 时间索引\n",
    "data_stock.index = pd.to_datetime(data_stock['date'])\n",
    "data_signals.index = pd.to_datetime(data_signals['date'])\n",
    "\n",
    "# backtrader投喂数据格式\n",
    "data_stock = data_stock.iloc[:, :6]\n",
    "data_stock['openinterest'] = 0\n",
    "\n",
    "# 初始化一个新的列来存储金叉死叉信号  \n",
    "data_signals['signal'] = 1 \n",
    "#================================================================================  \n",
    "# 使用shift函数来访问前一行的值，然后设置新的信号列  \n",
    "# 注意这里我们使用了向量化操作，而不是循环  \n",
    "data_signals.loc[(data_signals['M5'] > data_signals['M10'].shift(1)) &   \n",
    "               (data_signals['M5'].shift(1) < data_signals['M10'].shift(2)),   \n",
    "               'signal'] = 2 \n",
    "  \n",
    "data_signals.loc[(data_signals['M5'] < data_signals['M10'].shift(1)) &   \n",
    "               (data_signals['M5'].shift(1) > data_signals['M10'].shift(2)),   \n",
    "               'signal'] =0 \n",
    "  \n",
    "# 注意：这里我们使用了.shift(1)来访问前一行（即上一个交易日）的'M5'值  \n",
    "# 因为我们想要比较当前行的'M5'与前一行的'M5'  \n",
    "# data_signals = data_signals[['date', 'signal']]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_signals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 自定义数据源，这里仅展示框架，具体实现需要根据你的CSV文件格式进行调整  \n",
    "class MyStockData(bt.feeds.PandasData):  # MyStockData 类定义了如何从Pandas DataFrame中提取股票数据。\n",
    "    # params 元组指定了日期、开盘价、最高价、最低价、收盘价和交易量（如果有的话）在DataFrame中的列索引。\n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期在DataFrame中的列索引  \n",
    "        ('open', 1),      # 开盘价在DataFrame中的列索引  \n",
    "        ('high', 2),      # 最高价在DataFrame中的列索引  \n",
    "        ('low', 3),       # 最低价在DataFrame中的列索引  \n",
    "        ('close', 4),     # 收盘价在DataFrame中的列索引  \n",
    "        ('volume', 5),    # 交易量在DataFrame中的列索引（如果有的话）  \n",
    "        ('openinterest', -1),    # 交易量在DataFrame中的列索引（如果有的话）  \n",
    "    )  \n",
    "  \n",
    "class MySignalData(bt.feeds.PandasData):  # MySignalData 类用于处理信号数据。\n",
    "    lines = ('M5',\t'M10',\t'M20',\t'signal')  # lines = ('signal',) 是一个元组的示例，\n",
    "    # 指定了日期和信号在DataFrame中的列索引。\n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期在DataFrame中的列索引  \n",
    "        ('M5', 1),\n",
    "        ('M10', 2),\n",
    "        ('M20', 3),\n",
    "        ('signal', 4),    # 信号在DataFrame中的列索引，假设1为买入，-1为卖出，0为不操作  \n",
    "    )  \n",
    "#===============================================================================\n",
    "# 在策略中， self.datas[0] 访问股票数据，通过 self.datas[1] 访问信号数据。\n",
    "class MyStrategy(bt.Strategy):  \n",
    "    # params这个参数用于控制是否打印日志到控制台。\n",
    "    params = (  \n",
    "        ('printlog', True),  \n",
    "    )  # 只包含一个 两元素的子元组('printlog', True) 的元组。可以通过索引来访问这个子元组及其内部的元素。\n",
    "\n",
    "    # 用于记录日志的函数\n",
    "    def log(self, txt, dt=None):  \n",
    "        if self.params.printlog:  \n",
    "            dt = dt or self.datas[0].datetime.datetime(0)  \n",
    "            print(f'{dt}, {txt}')  \n",
    "  \n",
    "    def __init__(self):  \n",
    "        # 初始化策略  \n",
    "        self.dataclose = self.datas[0].close  \n",
    "        self.datasignal = self.datas[1].signal  \n",
    "  \n",
    "        # 可以在这里添加任何初始化代码  \n",
    "  \n",
    "    def next(self):  \n",
    "        # 简单示例：根据信号进行买入或卖出操作, 如果datasignal的当前值（self.datasignal[0]）...\n",
    "        if self.datasignal[0] == 2:  # 如果信号为1，买入  \n",
    "            if not self.position:  # 如果没有持仓  \n",
    "                self.log('BUY CREATE, %.2f' % self.dataclose[0])  \n",
    "                self.buy(size=300)  \n",
    "        elif self.datasignal[0] == 0:  # 如果信号为-1，卖出  \n",
    "            if self.position:  # 如果有持仓  \n",
    "                self.log('SELL CREATE, %.2f' % self.dataclose[0])  \n",
    "                self.sell(size=300) \n",
    "#==============================================================================="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建Cerebro引擎  \n",
    "cerebro = bt.Cerebro()  \n",
    "\n",
    "# # 设置回测时间范围\n",
    "start_date = datetime(2000, 10, 5)\n",
    "end_date = datetime(2024, 6, 7)\n",
    "  \n",
    "# 添加数据源  \n",
    "datafeed_stock = MyStockData(dataname=data_stock, fromdate=start_date, todate=end_date)  \n",
    "datafeed_signal = MySignalData(dataname=data_signals, fromdate=start_date, todate=end_date)  \n",
    "\n",
    "# 将数据源添加到cerebro  \n",
    "cerebro.adddata(datafeed_stock, name=\"000001\")  \n",
    "cerebro.adddata(datafeed_signal, name=\"zb\")  \n",
    "  \n",
    "# 添加策略  \n",
    "cerebro.addstrategy(MyStrategy)  \n",
    "  \n",
    "# 设置初始资金和手续费\n",
    "start_cash = 1000000\n",
    "cerebro.broker.setcash(start_cash)\n",
    "#cerebro.broker.setcommission(commission=0.002)\n",
    "\n",
    "# 添加策略分析指标\n",
    "cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='tradeanalyzer')  \n",
    "cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annualReturn')  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='annualizedReturns', tann=252)  # 使用 'annualizedReturns' 代替 '_Returns'  \n",
    "cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')  \n",
    "cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpeRatio')  # 使用 'sharpeRatio' 代替 'sharpe'  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='totalReturns')  # 使用 'totalReturns' 代替重复的 'returns'  \n",
    "# 假设 'TimeReturn' 是有效的分析器  \n",
    "cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='timeReturn')  # 假设这是有效的\n",
    "  \n",
    "# 运行回测  \n",
    "results=cerebro.run()  \n",
    "\n",
    "# 输出结果  \n",
    "#获取回测结果并打印\n",
    "port_value = cerebro.broker.getvalue()\n",
    "pnl = port_value - start_cash\n",
    "print(f\"初始资金: {start_cash}\\n回测期间：{start_date.strftime('%Y%m%d')}:{end_date.strftime('%Y%m%d')}\")\n",
    "print(f\"总资金: {round(port_value, 2)}\")\n",
    "print(f\"净收益: {round(pnl, 2)}\")#计算胜率\n",
    "total_trades = results[0].analyzers.tradeanalyzer.get_analysis()['total']['total']\n",
    "won_trades = results[0].analyzers.tradeanalyzer.get_analysis()['won']['total']\n",
    "win_rate = (won_trades / total_trades) * 100 if total_trades > 0 else 0\n",
    "print('总交易次数:', total_trades)\n",
    "print('盈利次数:', won_trades)\n",
    "print('胜率%:', win_rate)\n",
    "# 打印分析器输出结果\n",
    "# print(f\"初始资金: {start_cash}\\n回测期间:{start_date.strftime('%Y-%m-%d')} : {end_date.strftime('%Y-%m-%d')}\")\n",
    "print('年度汇报:', results[0].analyzers.annualReturn.get_analysis())\n",
    "print('年化收益%:', results[0].analyzers.annualizedReturns.get_analysis()['rnorm100'])\n",
    "print('最大回撤比例%:', results[0].analyzers.drawdown.get_analysis().max.drawdown)\n",
    "print('夏普比率:', results[0].analyzers.sharpeRatio.get_analysis()['sharperatio'])\n",
    "print('累计收益：', results[0].analyzers.totalReturns.get_analysis()['rtot'])\n",
    "\n",
    "print(f'最后投资金额：{round(cerebro.broker.getvalue(), 2)}')\n",
    "\n",
    "plotconfig = {\n",
    "    'id:ind#0': dict(\n",
    "        subplot=True,\n",
    "    ),\n",
    "}\n",
    "b = Bokeh(style='line', scheme=Tradimo(),plotconfig=plotconfig)\n",
    "cerebro.plot(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1 2 3 4 5 6 4 5 6]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "a = np.array([[1, 2, 3], [4, 5, 6], [4, 5, 6]])\n",
    "\n",
    "print(a.reshape(9))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pybroker",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
